package org.jkuang.qstar.commons.lock;

import java.net.Inet4Address;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import javax.sql.rowset.CachedRowSet;

import org.apache.log4j.Logger;
import org.jkuang.qstar.commons.db.ConnFactory;

/**
 * 
 * @author jkuang 2018年10月30日
 *
 */
public class IndexLock {

	/** 同一台服务器上确保只有一个实例在运行 */
	static String time = new SimpleDateFormat("'_'yyyy-MM-dd'T'HH:mm:ssSSS").format(new Date());
	/** log */
	static Logger logger = Logger.getLogger(IndexLock.class);
	/** 初始化业务锁 */
	static String CREATE_BUSS_LOCK = "REPLACE INTO indexlock(tablename,hostname,validity,sysdate)VALUES(?,?,0,NOW())";
	/** 初始化锁结构 **/
	static String CREATE_INDEX_LOCK = "CREATE TABLE IF NOT EXISTS indexlock(tablename VARCHAR(128) NOT NULL PRIMARY KEY,hostname VARCHAR(128),sysdate DATETIME,validity int)ENGINE=INNODB";
	/** 存储某个连接上是否已经创建过锁结构 */
	static volatile Map<String, Date> TABLE_LOCKS = new ConcurrentHashMap<String, Date>();
	/** 存储某个业务是否已经初始化过锁信息 */
	static volatile Map<String, Date> BUSS_LOCKS = new ConcurrentHashMap<String, Date>();

	/**
	 * 初始化库级别的锁结构
	 * 
	 * @param connName
	 */
	public synchronized static void createIndexLcokTable(String connName) {
		if (!TABLE_LOCKS.containsKey(connName)) {
			creeateTable(connName, CREATE_INDEX_LOCK);
			TABLE_LOCKS.put(connName, new Date());
		}
	}

	public synchronized static void creeateTable(String connName, String sql) {
		Connection conn = null;
		Statement statement = null;
		try {
			conn = ConnFactory.getConnection(connName);
			statement = conn.createStatement();
			System.out.println(sql);
			statement.execute(sql);

			TABLE_LOCKS.put(connName, new Date());
		} catch (SQLException e) {
			e.printStackTrace();
			logger.error(e.getMessage(), e);
		} finally {
			ConnFactory.close(statement);
			ConnFactory.close(conn);
		}
	}

	/**
	 * 初始化业务锁
	 * 
	 * @param conn
	 * @param key
	 */
	public static void initBussIndexLcok(String appName, Connection conn, String connName, String key, boolean force) {
		// 新启动的程序具有优先抢占资源的策略，故新启动的程序会占有相关资源
		if ((!BUSS_LOCKS.containsKey(connName + "_" + key)) || force) {
			/*
			 * if(hasInited(conn, connName, key)) { BUSS_LOCKS.put(connName+"_"+key, new
			 * Date()); return; }
			 */
			PreparedStatement statement = null;
			try {
				statement = conn.prepareStatement(CREATE_BUSS_LOCK);
				statement.setString(1, key);
				statement.setString(2, getHostName(appName));
				statement.execute();
				BUSS_LOCKS.put(connName + "_" + key, new Date());
			} catch (SQLException e) {
				logger.error(e.getMessage(), e);
			} finally {
				ConnFactory.close(statement);
			}
		}
	}

	public static boolean hasInited(Connection conn, String connName, String key) {
		String sql = "select hostname,sysdate,now() as curtime from indexlock where tablename=?  ";
		CachedRowSet rowSet = null;
		try {
			rowSet = ConnFactory.getCachedRowSet(sql);
			rowSet.setString(1, key);
			rowSet.execute(conn);
			if (rowSet.next()) {
				return true;
			} else {
				return false;
			}
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
			return false;
		} finally {
			ConnFactory.close(rowSet);
		}
	}

	/**
	 * 
	 * @param connName
	 * @param key
	 * @param validity 锁有效期，单位秒
	 * @return
	 * @throws SQLException
	 */
	public static boolean tryLockAndRelease(String appName, String connName, String key, int validity)
			throws SQLException {
		Connection conn = getLock(appName, connName, key);
		if (conn != null) {
			releaseLock(appName, conn, key, true, validity);
			ConnFactory.close(conn);
			return true;
		} else {
			return false;
		}
	}

	/**
	 * 获取业务锁
	 * 
	 * @param connName
	 * @param key
	 * @return
	 * @throws SQLException
	 */
	public static Connection getLock(String appName, String connName, String key) throws SQLException {
		logger.info("try getLock :" + key);
		createIndexLcokTable(connName);
		Connection conn = ConnFactory.getConnection(connName);
		ResultSet resultSet = null;
		PreparedStatement statement = null;
		try {
			initBussIndexLcok(appName, conn, connName, key, false);
			conn.setAutoCommit(false);
			conn.getTransactionIsolation();
			String sql = "select hostname,sysdate,now() as curtime,validity from indexlock where tablename=?  for update";
			statement = conn.prepareStatement(sql);
			statement.setString(1, key);
			resultSet = statement.executeQuery();
			if (resultSet.next()) {
				String hostname = resultSet.getString("hostname");
				Timestamp sysdate = resultSet.getTimestamp("sysdate");
				Timestamp curtime = resultSet.getTimestamp("curtime");
				int tempValidity = resultSet.getInt("validity");
				// 如果获取到锁的不是本机
				if ((!getHostName(appName).equals(hostname)) && sysdate != null) {
					long sys = sysdate.getTime();
					long now = curtime.getTime();
					// 如果在三分钟内该业务有程序占用，则放弃锁
					if (now - sys < 1000l * tempValidity) {
						releaseLock(appName, conn, key, false, 0);
						ConnFactory.close(conn);
						SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
						logger.info(key + " 该业务在 被服务:" + hostname + " 占用,有效期到："
								+ sdf2.format(new Date(sys + 1000l * tempValidity)) + ",本实例放弃资源使用！");
						System.out.println(key + " 该业务在 被服务:" + hostname + " 占用,有效期到："
								+ sdf2.format(new Date(sys + 1000l * tempValidity)) + ",本实例放弃资源使用！");
						return null;
					}
				}
				return conn;
			} else {
				initBussIndexLcok(appName, conn, connName, key, true);
				releaseLock(appName, conn, key, true, 0);
				ConnFactory.close(conn);
				return null;
			}
		} catch (Exception e) {
			e.printStackTrace();
			logger.info("get lock fail! lock:" + key);
			releaseLock(appName, conn, key, false, 0);
			ConnFactory.close(conn);
			return null;
		} finally {
			ConnFactory.close(resultSet);
			ConnFactory.close(statement);
		}
	}

	/**
	 * 
	 * @param conn
	 * @param key
	 * @param commit
	 * @param validity 有效期
	 */
	public static void releaseLock(String appName, Connection conn, String key, boolean commit, int validity) {
		logger.info("releaseLock :" + key + " use locked:" + commit);
		if (conn != null) {
			try {
				if (commit) {
					String sql = "update indexlock set hostname = ? ,validity= ? , sysdate=now()  where tablename=? ";
					PreparedStatement statement = null;
					try {
						statement = conn.prepareStatement(sql);
						statement.setString(1, getHostName(appName));
						statement.setInt(2, validity);
						statement.setString(3, key);
						statement.executeUpdate();
					} catch (Exception e) {
						e.printStackTrace();
						logger.error(e.getMessage(), e);
					}
					conn.commit();
				} else {
					conn.rollback();
				}
			} catch (Exception e) {
				// TODO: handle exception
			}
		}
	}

	/**
	 * 获取本实例的唯一标识
	 * 
	 * @return
	 */
	public static String getHostName(String appName) {
		try {
			String hostname = Inet4Address.getLocalHost().getHostName();
			if (hostname == null || hostname.length() == 0) {
				return "127.0.0.1_" + appName + time;
			} else {
				return hostname + "_" + appName + time;
			}
		} catch (Exception e) {
			return "127.0.0.1_" + appName + time;
		}
	}

}
